import actions from '@/components/actions'
import utils from '@/components/utils'

export default {
  props: {
    carousel: {
      type: [Object, Array, String],
      validate(value) {
        if (!value) {
          return true
        }
        if (typeof value === 'string') {
          return value in value.actions
        }

        if (Array.isArray(value)) {
          value = {
            actions: value
          }
        }

        if (!value.actions) {
          return true
        }
        for (const action in value.actions) {
          if (!actions[action]) {
            return false
          }
        }
        return true
      }
    }
  },
  data() {
    return {
      carouselUndoList: [],
      carouselToken: '',
      carouselDataIndex: 0
    }
  },
  mounted() {
    if (this.carousel) {
      this.readyCallbacks.push(this.initCarousel)
    }
  },
  beforeDestroy() {
    if (!this.carousel) {
      return
    }
    utils.cancelWait(this.carouselToken)
    this.getChart().then(chart => {
      if (!chart || chart.isDisposed()) {
        return
      }
      chart.off('mouseover', this.onMouseOverChart)
      chart.off('globalout', this.onMouseOutChart)
    })
  },
  computed: {
    carouselOption() {
      const option = {
        interval: 5000,
        mouse: true,
        actions: []
      }
      if (Array.isArray(this.carousel)) {
        option.actions = this.carousel
      } else if (typeof this.carousel === 'string') {
        option.actions = [{
          type: this.carousel
        }]
      } else {
        Object.assign(option, this.carousel)
      }
      // 将 geoSelect 放到 action 开始处
      // 将 showTip 放到 action 末尾处
      // 以防止操作结果被覆盖
      // TODO 是否还压根限制每种操作只能出现一次？
      let geoSelect
      let showTip

      const actions = []

      for (const action of option.actions) {
        if (action.type === 'showTip') {
          showTip = action
          continue
        }
        if (action.type === 'geoSelect') {
          geoSelect = action
          continue
        }
        actions.push(action)
      }

      if (geoSelect) {
        actions.unshift(geoSelect)
      }
      if (showTip) {
        actions.push(showTip)
      }

      option.actions = actions

      return option
    },
    carouselActions() {
      const actions = Array.isArray(this.carouselOption.actions) ? this.carouselOption.actions : [this.carouselOption.actions]
      actions.forEach(action => {
        if (!action.seriesIndex) {
          action.seriesIndex = [0]
        }
        switch (action.type) {
          case 'geoSelect':
            // geo 本身没有数据，只能操作地图数据
            action.nextItem = this.getNextGeoCarouselData
            break
          case 'legendSelect':
            action.nextItem = this.getNextLegendCarouselData
            break
          default:
            action.nextItem = this.getNextSeriesCarouselData
            break
        }
      })
      return actions
    }
  },
  methods: {
    initCarousel(chart) {
      if (this.carouselOption.mouse) {
        chart.on('mouseover', this.onMouseOverChart)
        chart.on('globalout', this.onMouseOutChart)
      }
      this.runCarousel(chart)
    },
    runCarousel(chart) {
      if (!chart || chart.isDisposed()) {
        return
      }
      requestAnimationFrame(() => {
        // 取消上次的执行
        this.undoCarousel(chart)
        for (const action of this.carouselActions) {
          let next = action.nextItem(this.carouselDataIndex, chart, action)
          let type
          if (!next) {
            if (action.type === 'legendSelect') {
              this.carouselDataIndex = -1
              type = 'legendAllSelect'
            } else {
              this.carouselDataIndex = 0
              next = action.nextItem(this.carouselDataIndex, chart, action)
            }
          }
          const actionValue = {
            ...action,
            ...next
          }
          if (type) {
            actionValue.type = type
          }
          delete actionValue.nextItem
          delete actionValue.undo

          setTimeout(() => {
            // 使用 setTimeout 使 dispatchAction 不同时执行，以避免一些操作结果被覆盖
            // 典型例子：
            // 在同时存在 geoSelect 和 showTip 时，geoSelect 会导致 tooltip 自动消失
            chart.dispatchAction(actionValue)
          }, 0)

          if (!action.undo) {
            continue
          }
          this.carouselUndoList.push({
            ...actionValue,
            type: actions[actionValue.type]
          })
        }
      })

      this.carouselToken = new Date().getTime()
      utils.wait(this.carouselOption.interval, this.carouselToken).then(() => {
        this.carouselToken = ''
        this.carouselDataIndex++
        requestAnimationFrame(() => {
          this.runCarousel(chart)
        })
      }).catch(() => {
        this.carouselToken = ''
      })
    },
    undoCarousel(chart) {
      for (const action of this.carouselUndoList) {
        chart.dispatchAction(action)
      }
      this.carouselUndoList.length = 0
    },
    onMouseOverChart() {
      if (!this.carouselToken) {
        return
      }
      this.getChart().then(chart => {
        this.undoCarousel(chart)
        utils.cancelWait(this.carouselToken)
        this.carouselToken = ''
      })
    },
    onMouseOutChart() {
      if (this.carouselToken) {
        return
      }
      this.getChart().then(chart => {
        this.runCarousel(chart)
      })
    },
    getNextGeoCarouselData(index, chart, action) {
      if (!this.computedOption.geo) {
        throw Error('Component "geo" is required for action "geoSelect"')
      }
      const geo = this.computedOption.geo[action.seriesIndex[0]]
      const data = window.echarts.getMap(geo.map).geoJson.features
      if (index >= data.length) {
        return null
      }
      return {
        name: data[index].properties.name
      }
    },
    getNextSeriesCarouselData(index, chart, action) {
      if (action.type === 'showTip') {
        if (!this.computedOption.tooltip) {
          throw Error('Component "tooltip" is required for action "showTip"')
        }
      }
      if (!this.computedOption.series) {
        return null
      }
      const series = this.computedOption.series[action.seriesIndex[0]]
      const data = series.data
      if (index >= data.length) {
        return null
      }
      return {
        dataIndex: index
      }
    },
    getNextLegendCarouselData(index, chart, action) {
      if (!this.computedOption.legend) {
        throw Error('Component "legend" is required for action "legendSelect"')
      }
      const data = this.computedOption.legend[action.seriesIndex[0]].data || this.computedOption.series.map(s => s.name)
      if (index >= data.length) {
        return null
      }
      return {
        name: data[index]
      }
    }
  }
}
